/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.simpleel.util;

import static org.junit.Assert.*;
import java.util.*;
import org.junit.*;
import ch.simpleel.data.*;
import ch.simpleel.util.ListMergeHelper.IMergeHandler;

/**
 * Test for {@link ListMergeHelper}.
 * 
 * @author M. Hautle
 */
public class ListMerge {
    /**
     * Creates the specified number of elements, differing in their {@link ABean#getValue()} property.
     * 
     * @param number The number of elements to create
     * @return The created elements
     */
    private List<ABean> init(int number) {
        final List<ABean> src = new ArrayList<ABean>();
        for (int i = 0; i < number; i++) {
            final ABean b = new ABean();
            b.setValue(i);
            src.add(b);
        }
        return src;
    }

    /**
     * Converts the given elements.<br>
     * Only {@link ABean#getValue()} will be considered.
     * 
     * @param doms The elements
     * @return The converted elements
     */
    private List<ABeanTO> convert(List<ABean> doms) {
        final List<ABeanTO> list = new ArrayList<ABeanTO>();
        for (ABean dom : doms) {
            final ABeanTO b = new ABeanTO();
            b.setValue(dom.getValue());
            list.add(b);
        }
        return list;
    }

    /**
     * Test for {@link ListMergeHelper#getElement(Object)} and {@link ListMergeHelper#getElementByKeys(Object...)}.
     */
    @Test
    public void testGetElement() {
        final List<ABean> src = init(5);
        final List<ABeanTO> res = convert(src);
        Collections.shuffle(src);
        Collections.shuffle(res);
        final CrossElementComparator<ABean> cmp = new CrossElementComparator<ABean>(ABean.class, ABean.ATTR_VALUE);
        final ListMergeHelper<ABean> merge = new ListMergeHelper<ABean>(cmp).addElements(src);
        cmp.addAccessors(ABeanTO.class, ABeanTO.ATTR_VALUE);
        for (ABeanTO t : res)
            check(t, merge.getElement(t));
        for (ABeanTO t : res)
            check(t, merge.getElementByKeys(new Integer(t.getValue())));
    }

    /**
     * Test for {@link ListMergeHelper#getElement(Object)} and {@link ListMergeHelper#getElementByKeys(Object...)}.
     */
    @Test
    public void testMiscMethods() {
        final List<ABean> src = init(5);
        Collections.shuffle(src);
        final CrossElementComparator<ABean> cmp = new CrossElementComparator<ABean>(ABean.class, ABean.ATTR_VALUE);
        final ListMergeHelper<ABean> merge = new ListMergeHelper<ABean>(cmp).addElements(src.toArray(new ABean[src.size()]));
        cmp.addAccessors(ABeanTO.class, ABeanTO.ATTR_VALUE);
        final List<ABean> res = merge.appendAllMerged(new ArrayList<ABean>());
        assertEquals(src.size(), res.size());
        res.removeAll(src);
        assertTrue(res.isEmpty());
    }

    /**
     * Test for {@link ListMergeHelper#removeAll(Collection)} and {@link ListMergeHelper#removeAllMerged(Collection)}.
     */
    @Test
    @SuppressWarnings("unchecked")
    public void testRemoveAll() {
        final List<ABean> src = init(5);
        final List<ABean> res = new ArrayList<ABean>(src);
        final List<ABeanTO> rem = new ArrayList<ABeanTO>(convert(src));
        final ABeanTO surv = rem.remove(0);
        Collections.shuffle(src);
        Collections.shuffle(res);
        final CrossElementComparator<ABean> cmp = new CrossElementComparator<ABean>(ABean.class, ABean.ATTR_VALUE);
        final ListMergeHelper<ABean> merge = new ListMergeHelper<ABean>(cmp).addElements(src);
        cmp.addAccessors(ABeanTO.class, ABeanTO.ATTR_VALUE);
        merge.removeAll(rem);
        assertTrue("Not expected size", merge.getSize() == 1);
        check(surv, merge.getElement(surv));
        assertTrue("Element not removed", merge.removeAllMerged(res).size() == 1);
        assertTrue("Not expected size", rem.size() == res.size());
        Collections.sort(res, cmp);
        Collections.sort(rem, (Comparator) cmp);
        for (int i = 0, cnt = res.size(); i < cnt; i++)
            check(rem.get(i), res.get(i));
    }

    /**
     * Test for {@link ListMergeHelper#retainAll(Collection)} and {@link ListMergeHelper#retainAllMerged(Collection)}.
     */
    @Test
    @SuppressWarnings("unchecked")
    public void testRetainAll() {
        final List<ABean> src = init(10);
        final List<ABean> res = new ArrayList<ABean>(src);
        final List<ABeanTO> ret = new ArrayList<ABeanTO>(convert(src));
        Collections.shuffle(src);
        Collections.shuffle(res);
        Collections.shuffle(ret);
        final CrossElementComparator<ABean> cmp = new CrossElementComparator<ABean>(ABean.class, ABean.ATTR_VALUE);
        final ListMergeHelper<ABean> merge = new ListMergeHelper<ABean>(cmp).addElements(src);
        cmp.addAccessors(ABeanTO.class, ABeanTO.ATTR_VALUE);
        for (int i = 0; i < 5; i++)
            ret.remove(0);
        // first we remove all 'deleted' TO's
        merge.retainAll(ret);
        assertTrue("Not expected size", merge.getSize() == ret.size());
        for (ABeanTO t : ret)
            check(t, merge.getElement(t));
        // now we adapt the res list so that it's equals to ret
        merge.retainAllMerged(res);
        assertTrue("Not expected size", res.size() == ret.size());
        Collections.sort(res, cmp);
        Collections.sort(ret, (Comparator) cmp);
        for (int i = 0, cnt = res.size(); i < cnt; i++)
            check(ret.get(i), res.get(i));
    }

    /**
     * Test for {@link ListMergeHelper#retainAll(Collection)} and {@link ListMergeHelper#retainAllMerged(Collection)} by using empty collections.
     */
    @Test
    public void testRetainAllEmpty() {
        final List<ABean> src = init(10);
        Collections.shuffle(src);
        final CrossElementComparator<ABean> cmp = new CrossElementComparator<ABean>(ABean.class, ABean.ATTR_VALUE);
        final ListMergeHelper<ABean> merge = new ListMergeHelper<ABean>(cmp).addElements(src);
        cmp.addAccessors(ABeanTO.class, ABeanTO.ATTR_VALUE);
        merge.retainAll(Collections.emptyList());
        assertTrue("Not expected size", merge.getSize() == 0);
        merge.retainAllMerged(src);
        assertTrue("Not expected size", src.size() == 0);
    }

    /**
     * Tests to merge two lists.
     */
    @Test
    public void testMerge() {
        final List<ABean> src = init(10);
        final List<ABeanTO> ret = new ArrayList<ABeanTO>(convert(src));
        // drop first dom -> this will be added as to
        final ABean newOne = src.remove(0);
        // drop second to -> this dom has to be removed
        final ABeanTO removed = ret.remove(1);
        Collections.shuffle(src);
        Collections.shuffle(ret);
        final CrossElementComparator<ABean> cmp = new CrossElementComparator<ABean>(ABean.class, ABean.ATTR_VALUE);
        final ListMergeHelper<ABean> merge = new ListMergeHelper<ABean>(cmp).addElements(src);
        cmp.addAccessors(ABeanTO.class, ABeanTO.ATTR_VALUE);
        final MergeHelper merger = new MergeHelper(newOne, removed, src, ret);
        merge.merge(ret, merger);
        merger.finalCheck();
    }

    /**
     * Checks that the two given beans were equals in sense of their {@link ABean#getValue()} values.
     * 
     * @param to The to
     * @param dom The dom
     */
    private static void check(ABeanTO to, ABean dom) {
        assertEquals(dom.getValue(), to.getValue());
    }

    /**
     * Merge handler.
     * 
     * @author M. Hautle
     */
    private static class MergeHelper implements IMergeHandler<ABean, ABeanTO> {
        /** Flag to check if {@link #added(ABeanTO)} was called. */
        private boolean add = false;

        /** Flag to check if {@link #removed(ABean)} was called. */
        private boolean remove = false;

        /** The bean which is expected to be added. */
        private final ABean newOne;

        /** The bean which is expected to be removed. */
        private final ABeanTO removed;

        /** All DOMs concerned by the merge. */
        private List<ABean> doms;

        /** All TOs concerned by the merge. */
        private List<ABeanTO> tos;

        /**
         * @param newOne
         * @param removed
         * @param doms
         * @param tos
         */
        public MergeHelper(ABean newOne, ABeanTO removed, List<ABean> doms, List<ABeanTO> tos) {
            this.newOne = newOne;
            this.removed = removed;
            this.doms = new ArrayList<ABean>(doms);
            this.tos = new ArrayList<ABeanTO>(tos);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void added(ABeanTO el) {
            assertFalse(add);
            check(el, newOne);
            add = true;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void merged(ABeanTO src, ABean dst) {
            check(src, dst);
            doms.remove(dst);
            tos.remove(src);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void removed(ABean el) {
            assertFalse(remove);
            check(removed, el);
            remove = true;
        }

        /**
         * Performs some final checks.
         */
        public void finalCheck() {
            assertTrue("remove", remove);
            assertTrue("add", add);
            assertTrue("merge", doms.size() == 1);
            assertTrue("merge", tos.size() == 1);
            check(removed, doms.get(0));
            check(tos.get(0), newOne);
        }
    }
}
